package paint;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.File;
import java.util.ArrayList;
import java.util.Scanner;
import java.util.List;

import javax.swing.*;


/**
 * 
 * @author Dustin Striplin
 * @assignment 2 milestone 1
 * @professor John Mayer
 */
public class PaintPanel458 extends JPanel implements KeyListener {
	
    /**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private static final double ROTATION_INCREMENT = 2.0;
	private static final double BOUNDARY_MULTIPLIER = 1.1;
    private static final int[][] CUBE_EDGES = {{0,1},{1,2},{2,3},{3,4},{4,5},{5,6},{0,3},{0,5},{1,6},{4,7},
    											{6,7},{2,7}}; //the index of each edge of the cube
    															//based on how each vertex is added
    															//to the cube's list
	private int width;
    private int height;
    private int imageSize;
    private int[] pixels;
    private double[][] zBuffer;
    
    private Display display;


    
    PaintPanel458() {
    	File imageFile = promptForFile();
    	display = new Display(imageFile);//parse the file
        width = display.getWidth();
        height = display.getHeight();
        imageSize = width * height;
        pixels = new int[imageSize * 3];
        zBuffer = new double[height][width];
        setPreferredSize(new Dimension(width,height));
        setFocusable(true);
        addKeyListener(this);        
    }
    
    void drawPixel(int x, int y, int r, int g, int b) {
        pixels[(height-y-1)*width*3+x*3] = r;
        pixels[(height-y-1)*width*3+x*3+1] = g;
        pixels[(height-y-1)*width*3+x*3+2] = b;                
    }
    
    private void clearScreen() {
    	for(int i = 0; i < pixels.length; i++) {
    		pixels[i] = 254; //turn the entire screen white
    	}
    	
    	for(int row = 0; row < zBuffer.length; row++) {
    		for(int col = 0; col < zBuffer[0].length; col++) {
    			zBuffer[row][col] = (double)Integer.MAX_VALUE;
    		}
    	}
    }
    
    public void createImage() {
    	clearScreen();
    	ArrayList<WorldObject> objList = display.mObjects;
    	for(WorldObject obj: objList) {
    		WorldObject temp = display.project(obj);
    		temp.r = obj.r;
    		temp.g = obj.g; 
    		temp.b = obj.b;
    		if(temp.mPoints.size() == 2) {
    			drawLine(temp);
    		} else if(temp.mPoints.size() == 3) {
    			drawTriangle(temp);
    		} else if(temp.mPoints.size() == 8) {
    			drawWireFrameCube(temp);
    		} else if(temp.mPoints.size() == 36) {
    			drawSolidCube(temp);
    		}
    	}
    }
    
    private void drawLine(WorldObject obj) {
		Vector3D vectA = new Vector3D(obj.mPoints.get(0));

		Vector3D vectB = new Vector3D (obj.mPoints.get(1));
		drawLine(vectA, vectB, obj.r, obj.g, obj.b);

    }
    
    private void drawLine(Vector3D vectA, Vector3D vectB, int r, int g, int b) {
		ScanlineContainer sc = new ScanlineContainer(height);
		Point3D pA = convertToDisplay(vectA);
		Point3D pB = convertToDisplay(vectB);
		sc.addLine(pA, pB);
		writeScanlines(sc,r,g,b);
    }
    
    private void drawTriangle(WorldObject obj) {
		Vector4D vectA = obj.mPoints.get(0);
		Vector4D vectB = obj.mPoints.get(1);
		Vector4D vectC = obj.mPoints.get(2);
		drawTriangle(vectA, vectB, vectC, obj.r, obj.g, obj.b);
    }
    
    private void drawTriangle(Vector4D vectA, Vector4D vectB, Vector4D vectC, int r, int g, int b) {
		ScanlineContainer sc = new ScanlineContainer(height);
		//TODO DETERMINE SHADING HERE
		Point3D pA = convertToDisplay(new Vector3D(vectA));
		Point3D pB = convertToDisplay(new Vector3D(vectB));
		Point3D pC = convertToDisplay(new Vector3D(vectC));
		sc.addLine(pA, pB);
		sc.addLine(pA,pC);
		sc.addLine(pB,pC);
		writeScanlines(sc, r, g, b);
    }
    
    private void drawWireFrameCube(WorldObject obj) {
    	List<Vector4D> vertices = obj.mPoints;
    	for(int edge = 0; edge < CUBE_EDGES.length; edge++) {//draw each edge
    		Vector3D vectA = new Vector3D(vertices.get(CUBE_EDGES[edge][0]));
    		Vector3D vectB = new Vector3D(vertices.get(CUBE_EDGES[edge][1]));
    		drawLine(vectA, vectB, obj.r,obj.g,obj.b);
    	}
    }
    
    private void drawSolidCube(WorldObject obj) {
    	List<Vector4D> vertices = obj.mPoints;
    	
    	for(int i = 0; i < vertices.size(); i+=3) {
    		Vector4D vectA = vertices.get(i);
    		Vector4D vectB = vertices.get(i+1);
    		Vector4D vectC = vertices.get(i+2);
    		drawTriangle(vectA, vectB, vectC, obj.r, obj.g, obj.b);
    	}
    }
    
    
    private void writeScanlines(ScanlineContainer sc, int r, int g, int b) {
		XZPair[][] container = sc.getContainer();
		for(int y = 0; y < container.length; y++) {//start at bottom of screen 0
			if(container[y][ScanlineContainer.MIN_X_INDEX].x != XZPair.UNINITIALIZED) {
				//calculate xz slope
				double xzSlope = getScanlineSlope(container[y][ScanlineContainer.MIN_X_INDEX],container[y][ScanlineContainer.MAX_X_INDEX]);
				double z = container[y][ScanlineContainer.MIN_X_INDEX].z;
				//check if they are same
				for(int x = container[y][ScanlineContainer.MIN_X_INDEX].x; x <= container[y][ScanlineContainer.MAX_X_INDEX].x; x++) {
					if(x < width && x >= 0 && y < height && y >= 0){
						if(zBuffer[y][x] > z) {
							drawPixel(x, y, r, g, b);
							zBuffer[y][x] = z;
						}
					}
					z += xzSlope;
				}
			}
		}
    }
    
	private double getScanlineSlope(XZPair a, XZPair b) {
		double rise = b.z - a.z;
		double run = b.x - a.x;
		if(run == 0) {
			run =  Double.MIN_VALUE;
		}
		return rise/run;
	}

    
    /**
     * Project the 3d image onto a 2d plane
     * @param v the 3d vector
     * @return the projected vector
     */
    private Point3D convertToDisplay(Vector3D v) {
    	//Vector4D projected = Matrix.projectXY(v); //the z value has been dropped, so this is essentially a 2d vector
		int displayX = (int)((width-1)  * (v.getX()+1)/2.0);
		int displayY = (int)((height-1)  * (v.getY()+1)/2.0);
    	return new Point3D(displayX, displayY, v.getZ());
    }
    
    
    /**
     * Prompts the user for a file name, & keeps prompting until a valid filename is input.
     * @return a File for the given file name
     */
    private File promptForFile() {
    	System.out.print("Please enter file name: ");
    	Scanner scan = new Scanner(System.in);
    	File imageFile;
    	do {
    	String text = scan.nextLine();
    	imageFile = new File(text);
    	if(!imageFile.exists()) {
    		System.out.print("File not found. Please enter another file name: ");
    	}
    	} while(!imageFile.exists());
    	System.out.println("File found. Processing image.");  	
    	scan.close();
    	return imageFile;
    }
    
    public void paintComponent(Graphics g) {
        createImage();
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        WritableRaster raster = image.getRaster();
        raster.setPixels(0, 0, width, height, pixels);        
        g.drawImage(image, 0, 0, null);
    }
    
    public void keyPressed(KeyEvent e){
    	int keyCode = e.getKeyCode();
    	
    	if(e.getModifiers() == 0) {
	    	switch(keyCode) {
	    	case KeyEvent.VK_UP: 
	    	case KeyEvent.VK_W: 	display.rotateX(-ROTATION_INCREMENT); break;
	    	case KeyEvent.VK_DOWN: 
	    	case KeyEvent.VK_S: 	display.rotateX(ROTATION_INCREMENT); break;
	    	case KeyEvent.VK_LEFT:
	    	case KeyEvent.VK_A: 	display.rotateY(-ROTATION_INCREMENT); break;
	    	case KeyEvent.VK_RIGHT:
	    	case KeyEvent.VK_D: 	display.rotateY(ROTATION_INCREMENT); break;
	    	case KeyEvent.VK_Q: 	display.rotateZ(ROTATION_INCREMENT); break;
	    	case KeyEvent.VK_E: 	display.rotateZ(-ROTATION_INCREMENT); break;
	    	case KeyEvent.VK_P:		display.setFrustum(); break;
	    	case KeyEvent.VK_O:		display.setOrtho(); break; 
	    	case KeyEvent.VK_L:     display.left *= BOUNDARY_MULTIPLIER; display.recalculatePerspective(); break; //TODO left
	    	case KeyEvent.VK_R:     display.right *= BOUNDARY_MULTIPLIER;display.recalculatePerspective();break; //TODO right
	    	case KeyEvent.VK_T:     display.top *= BOUNDARY_MULTIPLIER;display.recalculatePerspective();break; //TODO top
	    	case KeyEvent.VK_B:     display.bottom *= BOUNDARY_MULTIPLIER;display.recalculatePerspective();break; //TODO bottom
	    	case KeyEvent.VK_N:     display.zNear *= BOUNDARY_MULTIPLIER;display.recalculatePerspective();break; //TODO near 
	    	case KeyEvent.VK_F:     display.zFar *= BOUNDARY_MULTIPLIER;display.recalculatePerspective();break; //TODO far
	    	default: //do nothing
	    	}
    	} else if (e.getModifiers() == InputEvent.SHIFT_MASK){
    		switch(keyCode) {
	    	case KeyEvent.VK_L:     display.left /= BOUNDARY_MULTIPLIER;display.recalculatePerspective();break;
	    	case KeyEvent.VK_R:     display.right /= BOUNDARY_MULTIPLIER;display.recalculatePerspective();break;
	    	case KeyEvent.VK_T:     display.top /= BOUNDARY_MULTIPLIER;display.recalculatePerspective();break;
	    	case KeyEvent.VK_B:     display.bottom /= BOUNDARY_MULTIPLIER;display.recalculatePerspective();break;
	    	case KeyEvent.VK_N:     display.zNear /= BOUNDARY_MULTIPLIER;display.recalculatePerspective();break;
	    	case KeyEvent.VK_F:     display.zFar /= BOUNDARY_MULTIPLIER;display.recalculatePerspective();break;
    		default: //do nothing
    		}
    	}
    	
        repaint(); 
    }

    public void keyReleased(KeyEvent e){
       
    }

    public void keyTyped(KeyEvent e) {
    } 
    


    public static void main(String args[]) {
        JFrame frame = new JFrame("Paint Demo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(new PaintPanel458());
        frame.pack();      
        frame.setLocationRelativeTo( null );
        frame.setVisible( true );
        
    }

}

